package com.gitee.wsl.ext.coroutines

import com.gitee.wsl.ext.K
import kotlinx.coroutines.CancellableContinuation
import kotlinx.coroutines.CloseableCoroutineDispatcher
import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.Job
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.async
import kotlinx.coroutines.cancelAndJoin
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.currentCoroutineContext
import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive
import kotlinx.coroutines.job
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.withTimeout
import timber.log.Timber
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
import kotlin.coroutines.suspendCoroutine


@OptIn(ExperimentalCoroutinesApi::class)
inline fun CloseableCoroutineDispatcher.use(block: (CloseableCoroutineDispatcher) -> Unit) {
    try {
        block(this)
    } finally {
        close()
    }
}

suspend inline fun <T> suspendCoroutineWithTimeout(
    timeMillis: Long,
    crossinline block: (CancellableContinuation<T>) -> Unit
) = if (timeMillis > 0) {
    withTimeout(timeMillis) {
        suspendCancellableCoroutine(block = block)
    }
} else {
    suspendCancellableCoroutine(block = block)
}


/**
 * Supervisor with empty coroutine exception handler ignoring all exceptions.
 */
@Suppress("FunctionName")
 fun SilentSupervisor(parent: Job? = null): CoroutineContext =
    SupervisorJob(parent) + CoroutineExceptionHandler { _, _ -> }


val defaultExceptionHandler = CoroutineExceptionHandler { _, throwable ->
    Timber.e("CoroutineException", throwable.message, throwable)
}

/**
 * Call this method in case you need to do something in common thread (like reading of file in JVM)
 */
suspend fun <T> K.doOutsideOfCoroutine(block: () -> T): T = suspendCoroutine {
    try {
        it.resume(block())
    } catch (e: Throwable) {
        it.resumeWithException(e)
    }
}

suspend fun <T : Job, R> T.use(body: suspend CoroutineScope.(T) -> R): R {
    return try {
        coroutineScope { body(this@use) }
    } finally {
        cancelAndJoin()
    }
}

suspend fun <T> K.withSupervisor(body: suspend CoroutineScope.(scope: CoroutineScope) -> T): T {
    val context = currentCoroutineContext()
    val supervisorJob = SupervisorJob(context.job)
    return try {
        coroutineScope {
            body(CoroutineScope(context + supervisorJob))
        }
    }
    finally {
        supervisorJob.cancelAndJoin()
    }
}

/**
 * 在无 [CoroutineScope] 的 suspend 函数中调用的 [onCancellation]
 *
 * @param block
 * @receiver
 */
suspend inline fun K.onSuspendCancellation(
    crossinline block: () -> Unit
) = coroutineScope {
    onCancellation(block)
}